<?php

/**
 * see imagefilter function for effects
 * see imagesetstyle, imagesetbrush
 * 
 */  

namespace RedMVC;

/**
 * Image class
 *
 * @category    RedMVC
 * @author      Jan Fischer, bitWorking <info@bitworking.de>
 */
class Image{
    
    protected $_img;
    protected $_xSize;
    protected $_ySize;
    protected $_imgFile;
    protected $_hasAlpha = false;

    public function __construct($xSize = null, $ySize = null){
        ini_set('memory_limit', '128M');
        
        if(null !== $xSize && null !== $ySize){
            $this->init($xSize, $ySize);
        }
    }

    public function init($xSize, $ySize){
        $this->_img         = imagecreatetruecolor($xSize, $ySize);
        $this->_xSize       = $xSize;
        $this->_ySize       = $ySize;
        $this->_hasAlpha    = true;
    }

    public static function fromFile($imgFile){
        $data = getimagesize($imgFile);
        if(!$data){
            throw new \Exception('Image: cannot create image from file');
        }
        if($data[2] < 1 || $data[2] > 3){
           throw new \Exception('Image: image type is not valid');
        }
        $img            = new self();
        $img->_xSize    = $data[0];
        $img->_ySize    = $data[1];
        if($data[2] == IMAGETYPE_GIF){
			$img->_img      = imagecreatefromgif($imgFile);
            $img->_hasAlpha = true;
		}
		else if($data[2] == IMAGETYPE_JPEG){
			$img->_img      = imagecreatefromjpeg($imgFile);
		}
		else if($data[2] == IMAGETYPE_PNG){            
			$img->_img      = imagecreatefrompng($imgFile);
            $img->_hasAlpha = true;
		}
        $img->_imgFile = $imgFile;
        return $img;
    }

    public function getImg(){
        return $this->_img;
    }

    protected function _createImg($xSize, $ySize){
        $img = imagecreatetruecolor($xSize, $ySize);
        if($this->_hasAlpha){
            imagecolortransparent($img, imagecolorallocatealpha($img, 0, 0, 0, 127));
            imagealphablending($img, false);
            imagesavealpha($img, true);
        }
        //imageantialias($img, true);
        return $img;
    }

    public function setSize($xSize, $ySize){
        if($xSize == $this->_xSize && $ySize == $this->_ySize){
            return;
        }        
        $img = $this->_createImg($xSize, $ySize);
        imagecopyresampled($img, $this->_img, 0, 0, 0, 0, $xSize, $ySize, $this->_xSize, $this->_ySize);
        $this->_img     = $img;
        $this->_xSize   = $xSize;
        $this->_ySize   = $ySize;
    }

    public function setWidth($xSize){
        $scaling        = $xSize/$this->_xSize;
        $ySize          = $this->_ySize * $scaling;
        $img            = $this->_createImg($xSize, $ySize);
        imagecopyresampled($img, $this->_img, 0, 0, 0, 0, $xSize, $ySize, $this->_xSize, $this->_ySize);
        $this->_img     = $img;
        $this->_xSize   = $xSize;
        $this->_ySize   = $ySize;
    }

    public function setHeight($ySize){
        $scaling        = $ySize/$this->_ySize;
        $xSize          = $this->_xSize * $scaling;
        $img            = $this->_createImg($xSize, $ySize);
        imagecopyresampled($img, $this->_img, 0, 0, 0, 0, $xSize, $ySize, $this->_xSize, $this->_ySize);
        $this->_img     = $img;
        $this->_xSize   = $xSize;
        $this->_ySize   = $ySize;
    }

    public function thumbnail($maxPx, $cropRatio=false, $forceMaxPx=true){
        $offsetX	= 0;
		$offsetY	= 0;

        $xSize      = $this->_xSize;
        $ySize      = $this->_ySize;

		if($cropRatio !== false){
            $ratioComputed		= $xSize / $ySize;
            if($ratioComputed < $cropRatio){
                // image height to large
                $ySize      = $xSize / $cropRatio;
                $offsetY    = ($this->_ySize - $ySize) / 2;
            }
            else if($ratioComputed > $cropRatio){
                // image width to large
                $xSize      = $ySize * $cropRatio;
                $offsetX    = ($this->_xSize - $xSize) / 2;
            }
		}

        $thumbWidth     = $xSize;
        $thumbHeight 	= $ySize;

        if($forceMaxPx || ($xSize > $maxPx) || ($ySize > $maxPx)){
    		if($xSize > $ySize){
    			$scaling        = $xSize/$maxPx;
    			$thumbHeight    = intval($ySize/$scaling);
    			$thumbWidth     = $maxPx;
    		}
            else{
    			$scaling        = $ySize/$maxPx;
    			$thumbWidth 	= intval($xSize/$scaling);
    			$thumbHeight    = $maxPx;
    		}
		}

		$img = $this->_createImg($thumbWidth, $thumbHeight);
        imagecopyresampled($img, $this->_img, 0, 0, $offsetX, $offsetY, $thumbWidth, $thumbHeight, $xSize, $ySize);
        $this->_img     = $img;
        $this->_xSize   = $thumbWidth;
        $this->_ySize   = $thumbHeight;
    }

    public function fill($hexColor){
        imagefilledrectangle($this->_img, 0, 0, $this->_xSize, $this->_ySize, $this->getColorId($hexColor));
    }

    public function text($fontFile, $fontSize, $text, $hexColor = '#000000', Drawing\Point2D $point = null, $angle = 0){
        if(null === $point){
            $point = new Drawing\Point2D();
        }
        if(null === $fontFile){
            imagestring($this->_img, $fontSize, $point->x, $point->y, $text, $this->getColorId($hexColor));
        }
        else{
            imagettftext($this->_img, $fontSize, $angle, $point->x, $point->y, $this->getColorId($hexColor), $fontFile, $text);
        }
    }

    public static function textImg($fontFile, $fontSize, $text, $padding = 0, $hexColorFont = '#000000', $hexColorBg = '#ffffff7f', $angle = 0){
        if(null === $fontFile){
            $box = array(
                'left'   => 0,
                'top'    => 0,
                'width'  => imagefontwidth($fontSize) * mb_strlen($text,'UTF-8'),
                'height' => imagefontheight($fontSize),
            );
        }
        else{
            $rect = imagettfbbox($fontSize, $angle, $fontFile, $text);
            $minX = min(array($rect[0], $rect[2], $rect[4], $rect[6]));
            $maxX = max(array($rect[0], $rect[2], $rect[4], $rect[6]));
            $minY = min(array($rect[1], $rect[3], $rect[5], $rect[7]));
            $maxY = max(array($rect[1], $rect[3], $rect[5], $rect[7]));
    
            $box = array(
                'left'   => abs($minX) - 1,
                'top'    => abs($minY) - 1,
                'width'  => $maxX - $minX,
                'height' => $maxY - $minY,
                'box'    => $rect
            );
        }
        
        $imgWidth   = $box["width"] + ($padding<<1);
        $imgHeight  = $box["height"] + ($padding<<1);

        $image = new self();
        $image->init($imgWidth, $imgHeight);
        $image->fill($hexColorBg);
        $image->text(
            $fontFile,
            $fontSize,
            $text,
            $hexColorFont,
            new Drawing\Point2D(
                $box["left"] + ($imgWidth / 2) - ($box["width"] / 2),
                $box["top"] + ($imgHeight / 2) - ($box["height"] / 2)
            ),
            $angle
        );
        return $image;
    }
    
    public function line(Drawing\Point2D $point1 = null, Drawing\Point2D $point2 = null, $hexColor = '#000000', $thickness = 1){
        if(null === $point1){
            $point1 = new Drawing\Point2D();
        }
        if(null === $point2){
            $point2 = new Drawing\Point2D($this->_xSize - 1, $this->_ySize - 1);
        }
        imagesetthickness($this->_img, $thickness);
        imageline($this->_img, $point1->x, $point1->y, $point2->x, $point2->y, $this->getColorId($hexColor));        
    }

    public function rectangle(Drawing\Point2D $point1 = null, Drawing\Point2D $point2 = null, $hexColor = '#000000', $thickness = 1){
        if(null === $point1){
            $point1 = new Drawing\Point2D();
        }
        if(null === $point2){
            $point2 = new Drawing\Point2D($this->_xSize - 1, $this->_ySize - 1);
        }
        if(null === $thickness){
            imagefilledrectangle($this->_img, $point1->x, $point1->y, $point2->x, $point2->y, $this->getColorId($hexColor));
        }
        else{
            imagesetthickness($this->_img, $thickness);
            imagerectangle($this->_img, $point1->x, $point1->y, $point2->x, $point2->y, $this->getColorId($hexColor));
        }
    }
    
    public function polygon(array $points, $hexColor = '#000000', $thickness = 1){
        if(null === $thickness){
            imagefilledpolygon($this->_img, $points, count($points) >> 1, $this->getColorId($hexColor));
        }
        else{
            imagesetthickness($this->_img, $thickness);
            imagepolygon($this->_img, $points, count($points) >> 1, $this->getColorId($hexColor));
        }
    }
    
    public function ellipse(Drawing\Point2D $centerPoint = null, $width = null, $height = null, $hexColor = '#000000', $thickness = 1){
        if(null === $centerPoint){
            $centerPoint = new Drawing\Point2D($this->_xSize >> 1, $this->_ySize >> 1);
        }
        if(null === $width){
            $width = $this->_xSize - 1;
        }
        if(null === $height){
            $height = $this->_ySize - 1;
        }
        if(null === $thickness){
            imagefilledellipse($this->_img, $centerPoint->x, $centerPoint->y, $width, $height, $this->getColorId($hexColor));
        }
        else{
            imagesetthickness($this->_img, $thickness);
            if(1 === $thickness){
                imageellipse($this->_img, $centerPoint->x, $centerPoint->y, $width, $height, $this->getColorId($hexColor));
            }
            // this is a PHP bug: thickness don't work with ellipse
            else{
                imagearc($this->_img, $centerPoint->x, $centerPoint->y, $width, $height, 0, 359.9, $this->getColorId($hexColor)); 
            }
        }
    }
    
    public function arc(Drawing\Point2D $centerPoint = null, $width = null, $height = null, $start = 0, $end = 359, $hexColor = '#000000', $thickness = 1){
        if(null === $centerPoint){
            $centerPoint = new Drawing\Point2D($this->_xSize >> 1, $this->_ySize >> 1);
        }
        if(null === $width){
            $width = $this->_xSize - 1;
        }
        if(null === $height){
            $height = $this->_ySize - 1;
        }
        if(null === $thickness){
            imagefilledarc($this->_img, $centerPoint->x, $centerPoint->y, $width, $height, $start, $end, $this->getColorId($hexColor));
        }
        else{
            imagesetthickness($this->_img, $thickness);            
            imagearc($this->_img, $centerPoint->x, $centerPoint->y, $width, $height, $start, $end, $this->getColorId($hexColor));
        }
    }
    

    /**
     * gets color id from hex color
     *
     * @param string $hexColor example: #ff0000 or ff0000
     * @return int
     */
    public function getColorId($hexColor){
        $rgb = str_split(ltrim($hexColor, '#'), 2);
        if(count($rgb) == 4){
            imagealphablending($this->_img, false);
            imagesavealpha($this->_img, true);
            return imagecolorallocatealpha(
                $this->_img,
                hexdec($rgb[0]),
                hexdec($rgb[1]),
                hexdec($rgb[2]),
                hexdec($rgb[3])
            );
        }
        return imagecolorallocate(
            $this->_img,
            hexdec($rgb[0]),
            hexdec($rgb[1]),
            hexdec($rgb[2])
        );
    }

    protected function _unlink($fileName){
        if(null !== $fileName && null !== $this->_imgFile && $fileName == $this->_imgFile){
            unlink($fileName);
        }
    }

    public function toGif($fileName = null){
        $this->_unlink($fileName);
        if(null === $fileName){
            header('Content-Type: image/gif');
        }
        imagegif($this->_img, $fileName);
    }

    public function toJpg($fileName = null, $quality = 75){
        $this->_unlink($fileName);
        if(null === $fileName){
            header('Content-Type: image/jpeg');
        }
        imagejpeg($this->_img, $fileName, $quality);
    }

    /**
     * saves image as png
     *
     * @param string|null $fileName
     * @param int $compression 0 (no compression) -> 9
     * @param int $filters
     */
    public function toPng($fileName = null, $compression = 0, $filters = PNG_NO_FILTER){
        $this->_unlink($fileName);
        if(null === $fileName){
            header('Content-Type: image/png');
        }
        imagepng($this->_img, $fileName, $compression, $filters);
    }

    public function __deconstruct(){
        if(null !== $this->_img){
            imagedestroy($this->_img);
        }
    }
}